package com.example.demo.common;

import com.example.demo.entity.config.CustomeParam;
import com.example.demo.common.constant.ResultCode;
import com.github.f4b6a3.ulid.UlidCreator;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.security.MessageDigest;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;

@Component
@RequiredArgsConstructor
public class ApiSign {

    private final RedisTemplate<String, String> redis;
    private final CustomeParam param;

    public TreeMap<String, Object> createSign(TreeMap<String, Object> map) {
        // 加入校验参数
        map.put("nonce", UlidCreator.getUlid().toString());
        map.put("timestamp", System.currentTimeMillis());
        map.put("key", param.getApiSignKeys());

        // 计算sign参数
        String param = "";
        for (Map.Entry<String, Object> item : map.entrySet()) {
            param += item.getKey() + "=" + item.getValue() + "&";
        }
        map.put("sign", hashData(param, "MD5"));

        return map;
    }

    public Result checkSign(TreeMap<String, Object> map) {
        Result res = new Result();

        // 获取传过来的appId，以此查询数据库获取appSecret（先查redis，若无则从MySQL获取，再将其存入redis）
        // 这是针对特定接口的，只需要将参数直接传过来就行，没必要刻意放到Header里

        // 1、检查 timestamp 是否超出允许的范围
        long timestamp = Long.parseLong(map.get("timestamp").toString());
        long allowDisparity = 1000 * 60 * 15;    // 允许的时间差：15分钟
        long timestampDisparity = Math.abs(System.currentTimeMillis() - timestamp);
        if (timestampDisparity > allowDisparity) {
            res.setDataError(ResultCode.SIGN_EXPIRE);
            return res;
        }

        // 2、检查此 nonce 是否已被使用过了（防重用的key可用check:sign:appId_nonce，注：nonce是ULID）
        String nonce = map.get("nonce").toString();
        if (redis.opsForValue().get("check:sign:" + nonce) != null) {
            res.setDataError(ResultCode.SIGN_USED);
            return res;
        }

        // 3、验证签名（注意参数拼接的顺序）
        // 拼接的参数：appSecret，timestamp，nonce，所有传参
        String sign = map.get("sign").toString();
        String param = "";
        for (Map.Entry<String, Object> item : map.entrySet()) {
            if (!item.getKey().equals("sign")) {
                param += item.getKey() + "=" + item.getValue() + "&";
            }
        }
        if (!sign.equals(hashData(param, "MD5"))) {
            res.setDataError(ResultCode.SIGN_ERROR);
            return res;
        }

        // 4、将 nonce 存入redis，防止重复使用，注意此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2
        redis.opsForValue().set("check:sign:" + nonce, "1", allowDisparity * 2, TimeUnit.SECONDS);

        return null;
    }

    public String hashData(String data, String algorithm) {
        try {
            MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
            byte[] hashBytes = messageDigest.digest(data.getBytes());

            // 将字节数组转换为十六进制字符串
            StringBuilder hexString = new StringBuilder();
            for (byte b : hashBytes) {
                String hex = Integer.toHexString(0xFF & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }

            return hexString.toString();
        }
        catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

}
